package com.clh.seckill.controller;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.clh.seckill.config.AccessLimit;
import com.clh.seckill.pojo.Order;
import com.clh.seckill.pojo.SeckillMessage;
import com.clh.seckill.pojo.SeckillOrder;
import com.clh.seckill.pojo.User;
import com.clh.seckill.rabbitmq.MQSender;
import com.clh.seckill.service.IGoodsService;
import com.clh.seckill.service.IOrderService;
import com.clh.seckill.service.ISeckillOrderService;
import com.clh.seckill.service.UserService;
import com.clh.seckill.vo.GoodsVo;
import com.clh.seckill.vo.RespBean;
import com.clh.seckill.vo.RespBeanEnum;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import org.springframework.stereotype.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**·
 * <p>
 *  前端控制器
 * </p>
 *
 * @author clh
 * @since 2021-11-12
 */
@Controller
@Slf4j
@RequestMapping("/seckill")
public class SeckillGoodsController implements InitializingBean {
    @Autowired
    UserService userService;
    @Autowired
    IGoodsService goodsService;
    @Autowired
    ISeckillOrderService iSeckillOrderService;
    @Autowired
    IOrderService orderService;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    ISeckillOrderService seckillOrderService;
    //注入生产者
    @Autowired
    MQSender mqSender;
    @Autowired
    @Qualifier("stockluaScript")
    DefaultRedisScript stockluaScript;
    //库存标识
    private Map<Long,Boolean> seckillMap=new HashMap();

    /**
     * 点击秒杀按钮
     * @return
     */
    @RequestMapping("/doSeckill/{path}")
    public String doSeckill(Model model, User user,Long goodsId,@PathVariable String path){
        model.addAttribute("user",user);
        Boolean isCkeck=seckillOrderService.checkPath(user,goodsId,path);
        if (!isCkeck){
            model.addAttribute("errorMeg", RespBeanEnum.LOGIN_ILLEGALITY.getMessage());
            return "secKillFail";
        }
        //通过内存标记减少redis访问
        if (seckillMap.get(goodsId)){
            model.addAttribute("errorMeg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "secKillFail";
        }
        //判断用户是否已经抢过
        SeckillOrder seckillOrder= (SeckillOrder)redisTemplate.opsForValue().get("order:"+user.getId()+":"+goodsId);
        if (seckillOrder!=null){
            model.addAttribute("errorMeg", RespBeanEnum.REPEATE_ERROR.getMessage());
            return "secKillFail";
        }
        //redis 预减库存
//        Long seckillCount = redisTemplate.opsForValue().decrement("SeckillGoods:" + goodsId);//redis递减
        //方式二：使用lua脚本实现分布式锁

        Long seckillCount = (Long) redisTemplate.execute(stockluaScript, Collections.singletonList("SeckillGoods:" + goodsId));
        if (seckillCount<1){
            seckillMap.put(goodsId,true);
//            redisTemplate.opsForValue().increment("SeckillGoods:" + goodsId);//redis递增
            model.addAttribute("errorMeg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "secKillFail";
        }
        //封装消息体
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        //开始进入队列排队
        mqSender.sendSeckillMessage(JSON.toJSONString(seckillMessage));
        model.addAttribute("goodsId",goodsId);

        /**
        //库存校验
        GoodsVo goodsVo = goodsService.findGoodsById(goodsId);
        if (goodsVo.getSeckillCount()<1){
            model.addAttribute("errorMeg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "secKillFail";
        }
        //判断用户是否已经抢过
//        SeckillOrder seckillOrder = iSeckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
        SeckillOrder seckillOrder= (SeckillOrder)redisTemplate.opsForValue().get("order:"+user.getId()+":"+goodsId);
        if (seckillOrder!=null){
            model.addAttribute("errorMeg", RespBeanEnum.REPEATE_ERROR.getMessage());
            return "secKillFail";
        }
        //开始抢购
        Order order =orderService.createSeckillOrder(user,goodsVo);
        model.addAttribute("goods",goodsVo);
        model.addAttribute("order",order);
        return "orderDetail";
         */
        return "seckillAwait";
    }

    /**
     * 验证码生成
     * @param user
     */
    @GetMapping("/captcha")
    public void verifCode(User user,Long goodsId, HttpServletRequest request, HttpServletResponse response)  {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        //不需要缓存
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        // 三个参数分别为宽、高、位数
        // 算术类型
        ArithmeticCaptcha specCaptcha = new ArithmeticCaptcha(130, 48);
        specCaptcha.setLen(3);
        //存入redis
        String result = specCaptcha.text();
        redisTemplate.opsForValue().set("captcha:"+user.getId()+":"+goodsId,result,60, TimeUnit.SECONDS);
        // 输出图片流
        try {
            specCaptcha.out(response.getOutputStream());
        } catch (IOException e) {
            redisTemplate.delete("captcha:"+user.getId()+":"+goodsId);
            log.error("验证码生成出错：",e);
        }
    }

    /**
     *
     * 在spring初始化的时候就要调用的方法
     * 1、Spring为bean提供了两种初始化bean的方式，
     *   实现InitializingBean接口，实现afterPropertiesSet方法，
     *   或者在配置文件中通过init-method指定，两种方式可以同时使用。
     * 2、实现InitializingBean接口是直接调用afterPropertiesSet方法，
     *   比通过反射调用init-method指定的方法效率要高一点，但是init-method方式消除了对spring的依赖
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //将所有的库存存入到redis中去
        List<GoodsVo> goods = goodsService.findGoods();
        goods.forEach(goodsVo ->{
            redisTemplate.opsForValue().set("SeckillGoods:"+goodsVo.getId(),goodsVo.getSeckillCount());
            seckillMap.put(goodsVo.getId(),false);
        }
        );
    }

    /**
     * 获取秒杀结果
     * @param user
     * @param goodsId
     * @return
     */
    @GetMapping("/result")
    @ResponseBody
    public RespBean getResult(User user,Long goodsId){
        Long result = seckillOrderService.getResult(user, goodsId);
        return RespBean.success(String.valueOf(result));
    }

    /**
     * 获取秒杀地址
     * @param user
     * @param goodsId
     * @param captcha 验证码
     * @return
     */
    @RequestMapping(method = RequestMethod.GET,value = "/path")
    @AccessLimit(second = 10,maxCount = 3)//自定义注解：操作三次以上后就禁止操10秒
    @ResponseBody
    public RespBean getSeckillPath(User user,long goodsId,String captcha){
        Boolean checkResult=seckillOrderService.checkCaptcha(user,goodsId,captcha);
        if (!checkResult){
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR, false);
        }

        String path = seckillOrderService.createPath(user, goodsId);
        return RespBean.success(path);
    }
}
